package utility

import (
	"context"
	"donkey-admin/internal/dao"
	"errors"
	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
	_ "github.com/go-sql-driver/mysql"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/v2/database/gdb"
	gdbadapter "github.com/hailaz/gf-casbin-adapter/v2"
	"strconv"
	"sync"
)

type CasbinInfo struct {
	Path   string `json:"path"`   // 路径
	Method string `json:"method"` // 方法
}

type casbinService struct{}

var CasbinUtils = new(casbinService)

// UpdateCasbin @author: [piexlmax](https://github.com/piexlmax)
// @function: UpdateCasbin
// @description: 更新casbin权限
// @param: authorityId string, casbinInfos []request.CasbinInfo
// @return: error
func (casbinService *casbinService) UpdateCasbin(ctx context.Context, AuthorityID string, casbinInfos []CasbinInfo) error {
	casbinService.ClearCasbin(ctx, 0, AuthorityID)
	var rules [][]string
	//做权限去重处理
	deduplicateMap := make(map[string]bool)
	for _, v := range casbinInfos {
		key := AuthorityID + v.Path + v.Method
		if _, ok := deduplicateMap[key]; !ok {
			deduplicateMap[key] = true
			rules = append(rules, []string{AuthorityID, v.Path, v.Method})
		}
	}
	e := casbinService.Casbin(ctx)
	success, _ := e.AddPolicies(rules)
	if !success {
		return errors.New("存在相同api,添加失败,请联系管理员")
	}
	return nil
}

// UpdateCasbinApi @author: [piexlmax](https://github.com/piexlmax)
// @function: UpdateCasbinApi
// @description: API更新随动
// @param: oldPath string, newPath string, oldMethod string, newMethod string
// @return: error
func (casbinService *casbinService) UpdateCasbinApi(ctx context.Context, oldPath string, newPath string, oldMethod string, newMethod string) error {

	_, err := dao.CasbinRule.Ctx(ctx).Where("v1", oldPath).Where("v2", oldMethod).Update(g.Map{
		"v1": newPath,
		"v2": newMethod,
	})
	if err != nil {
		g.Log().Error(ctx, err)
		return err
	}
	e := casbinService.Casbin(ctx)
	err = e.LoadPolicy()
	if err != nil {
		return err
	}
	return err
}

// GetPolicyPathByAuthorityId @author: [piexlmax](https://github.com/piexlmax)
// @function: GetPolicyPathByAuthorityId
// @description: 获取权限列表
// @param: authorityId string
// @return: pathMaps []request.CasbinInfo
func (casbinService *casbinService) GetPolicyPathByAuthorityId(ctx context.Context, AuthorityID uint) (pathMaps []CasbinInfo) {
	e := casbinService.Casbin(ctx)
	authorityId := strconv.Itoa(int(AuthorityID))
	list := e.GetFilteredPolicy(0, authorityId)
	for _, v := range list {
		pathMaps = append(pathMaps, CasbinInfo{
			Path:   v[1],
			Method: v[2],
		})
	}
	return pathMaps
}

// ClearCasbin @author: [piexlmax](https://github.com/piexlmax)
// @function: ClearCasbin
// @description: 清除匹配的权限
// @param: v int, p ...string
// @return: bool
func (casbinService *casbinService) ClearCasbin(ctx context.Context, v int, p ...string) bool {
	e := casbinService.Casbin(ctx)
	success, _ := e.RemoveFilteredPolicy(v, p...)
	return success
}

// RemoveFilteredPolicy @author: [piexlmax](https://github.com/piexlmax)
// @function: RemoveFilteredPolicy
// @description: 使用数据库方法清理筛选的politicy 此方法需要调用FreshCasbin方法才可以在系统中即刻生效
// @param: db *gorm.DB, authorityId string
// @return: error
func (casbinService *casbinService) RemoveFilteredPolicy(ctx context.Context, authorityId string) error {
	_, err := dao.CasbinRule.Ctx(ctx).Where("v0", authorityId).Delete()
	if err != nil {
		g.Log().Error(ctx, err)
	}
	return err
}

// SyncPolicy @author: [piexlmax](https://github.com/piexlmax)
// @function: SyncPolicy
// @description: 同步目前数据库的policy 此方法需要调用FreshCasbin方法才可以在系统中即刻生效
// @param: db *gorm.DB, authorityId string, rules [][]string
// @return: error
func (casbinService *casbinService) SyncPolicy(ctx context.Context, authorityId string, rules [][]string) error {
	err := casbinService.RemoveFilteredPolicy(ctx, authorityId)
	if err != nil {
		return err
	}
	return casbinService.AddPolicies(ctx, rules)
}

// AddPolicies @author: [piexlmax](https://github.com/piexlmax)
// @function: AddPolicies
// @description: 添加匹配的权限
// @param: v int, p ...string
// @return: bool
func (casbinService *casbinService) AddPolicies(ctx context.Context, rules [][]string) error {
	var casbinRules []gdbadapter.CasbinRule
	for i := range rules {
		casbinRules = append(casbinRules, gdbadapter.CasbinRule{
			PType: "p",
			V0:    rules[i][0],
			V1:    rules[i][1],
			V2:    rules[i][2],
		})
	}

	_, err := dao.CasbinRule.Ctx(ctx).Insert(casbinRules)
	if err != nil {
		g.Log().Error(ctx, err)
	}
	return err
}

func (casbinService *casbinService) FreshCasbin(ctx context.Context) (err error) {
	e := casbinService.Casbin(ctx)
	err = e.LoadPolicy()
	return err
}

var (
	syncedCachedEnforcer *casbin.SyncedEnforcer
	once                 sync.Once
)

// Casbin @author: [piexlmax](https://github.com/piexlmax)
// @function: Casbin
// @description: 持久化到数据库  引入自定义规则
// @return: *casbin.Enforcer
func (casbinService *casbinService) Casbin(ctx context.Context) *casbin.SyncedEnforcer {
	once.Do(func() {
		db, err := gdb.New(gdb.ConfigNode{Type: g.DB("default").GetConfig().Type, Link: g.DB("default").GetConfig().Link})
		if err != nil {
			g.Log().Error(ctx, err)
			return
		}
		a := gdbadapter.NewAdapter(gdbadapter.Options{
			GDB:       db,
			TableName: "casbin_rule",
		})

		m, err := model.NewModelFromString(`
		[request_definition]
		r = sub, obj, act
		
		[policy_definition]
		p = sub, obj, act
		
		[role_definition]
		g = _, _
		
		[policy_effect]
		e = some(where (p.eft == allow))
		
		[matchers]
		m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
		`)
		if err != nil {
			g.Log().Error(ctx, "字符串加载模型失败!", err.Error())
			return
		}
		syncedCachedEnforcer, err = casbin.NewSyncedEnforcer(m, a)
		if err != nil {
			g.Log().Error(ctx, err)
			return
		}
		err = syncedCachedEnforcer.LoadPolicy()
		if err != nil {
			g.Log().Error(ctx, err)
			return
		}
	})
	return syncedCachedEnforcer
}
